-
Notifications
You must be signed in to change notification settings - Fork 4.9k
Conversation
@@ -2499,9 +2499,6 @@ private bool TryProcessDone(SqlCommand cmd, SqlDataReader reader, ref RunBehavio | |||
ushort status; | |||
int count; | |||
|
|||
// Can't retry TryProcessDone | |||
stateObj._syncOverAsync = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this change specifically? Framework TdsParser also has this line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment // Can't retry TryProcessDone
seems interesting. Looks like syncOverAsync was set to true
to prevent a retry of processing the Done TDS token. Have you checked if this "retry" is not invoked after your changes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I checked, and TryProcessDone() was not retried after my change. However, I have just become curious what happens if I force it to retry TryProcessDone() by following code:
I meant , are there code paths which could retry Done token processing in certain scenarios for a single SqlDataReader Read operation, instead of multiple Read() operations, which needs to be prevented?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@saurabh500
TryProcessDone()
runs multiple times until ReadSni()
actually returns packets, and it is processed by TryProcessDone()
. Once DONE
token is processed, codeflow moves forward, and there is no code path to RETRY the DONE
token processing, which is already done.
@@ -0,0 +1,107 @@ | |||
// Licensed to the .NET Foundation under one or more agreements. | |||
// The .NET Foundation licenses this file to you under the MIT license. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test could be moved to AsyncTest.cs
public class ReadAsyncTest | ||
{ | ||
[CheckConnStrSetupFact] | ||
public static void Run() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Provide a more descriptive name for this Run() method.
{ | ||
double elapsedAsync = await RunReadAsync(); | ||
double elapsedSync = RunRead(); | ||
Assert.True(elapsedAsync < (elapsedSync / 2.0d)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test Asserts that Async should take half the time taken by Sync.
This doesn't seems right. Async should take close to 0 ms.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this assert be improved?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It took about 15~30ms in my environment for asynchronous operation.
For synchronous read, it took about 2000ms.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I checked, and TryProcessDone()
was not retried after my change. However, I have just become curious what happens if I force it to retry TryProcessDone()
by following code:
Task<bool> t1, t2;
do
{
t1 = reader.ReadAsync();
t2 = reader.ReadAsync();
}
while (await t1 && await t2);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It took about 15~30ms in my environment for asynchronous operation.
For synchronous read, it took about 2000ms.
Based on this can you provide a better Assert for this test.
My concern is that if ReadSync takes 30 seconds in some cases, then ReadAsync should still take ~30 ms. So a proportionate comparison with the time taken by ReadAsync and ReadSync doesn't make sense
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Task t1, t2;
do
{
t1 = reader.ReadAsync();
t2 = reader.ReadAsync();
}
while (await t1 && await t2);
This Code will not retry Done token processing in the middle of the TDS stream.
@@ -2499,9 +2499,6 @@ private bool TryProcessDone(SqlCommand cmd, SqlDataReader reader, ref RunBehavio | |||
ushort status; | |||
int count; | |||
|
|||
// Can't retry TryProcessDone | |||
stateObj._syncOverAsync = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment // Can't retry TryProcessDone
seems interesting. Looks like syncOverAsync was set to true
to prevent a retry of processing the Done TDS token. Have you checked if this "retry" is not invoked after your changes?
@@ -2499,9 +2499,6 @@ private bool TryProcessDone(SqlCommand cmd, SqlDataReader reader, ref RunBehavio | |||
ushort status; | |||
int count; | |||
|
|||
// Can't retry TryProcessDone | |||
stateObj._syncOverAsync = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What causes this issue?The issue resides in both .NET Framework and .NET Core.
Default value of When But Look at the stack trace again:
As result, |
using (SqlCommand command = connection.CreateCommand()) | ||
{ | ||
command.CommandText = commandText; | ||
using (SqlDataReader reader = command.ExecuteReader()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could use the async equivalent of ExecuteReader to make this method async. The same applies to connection.Open()
Why does this _syncOverAsync thing exist at all? Why was it added in the first place? Can we entirely delete it? |
@@ -36,7 +37,7 @@ public static void ExecuteTest() | |||
con.Close(); | |||
} | |||
|
|||
[CheckConnStrSetupFact] | |||
//[CheckConnStrSetupFact] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may want to clean this method and the one above. Looks like they are not used and the [Fact] attribute has been removed
CompareReadSyncAndReadAsync(); | ||
} | ||
|
||
private static async void CompareReadSyncAndReadAsync() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method doesn't need to be async
+ " DROP TABLE #y" | ||
+ " END" | ||
+ " SELECT 'b'"; | ||
double elapsedAsync = await RunReadAsync(sql); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be better to wait on the task returned by RunReadAsync(sql);
+ " SELECT 'b'"; | ||
double elapsedAsync = await RunReadAsync(sql); | ||
double elapsedSync = RunReadSync(sql); | ||
Assert.True(elapsedAsync < (elapsedSync / 2.0d)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you plan to improve this assert in future commits?
@stephentoub It seems like when the networking layer of SqlClient was changed in the framework (long ago), it was decided that Async APIs following the Begin and End pattern would use IOCP at the networking layer to make the calls so that the calls are not blocking. This behavior was put behind a flag called Async=true/false |
Thanks. After this fix, do any async paths still hit sync over async paths? And is this a contributor to the issue that made you make long running tasks, and if so, can we undo that now? |
@stephentoub |
[CheckConnStrSetupFact] | ||
public static void ExecuteTest() | ||
public static void TestReadAsync() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be changed to TestReadAsyncTimeConsumed
double elapsedSync = RunReadSync(sql); | ||
t.Wait(); | ||
double elapsedAsync = t.Result; | ||
Console.WriteLine("Asynchronous Operation: " + elapsedAsync + "ms"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove Console statements.
@saurabh500 Manual tests were also passed. |
Commit migrated from dotnet/corefx@da8a105
This fix is for the issue https://github.com/dotnet/corefx/issues/26594
Customer reported an issue for SqlDataReader.ReadAsync() method actually runs synchronously, and it blocks the calling thread until data is fed from SQL Server.